﻿namespace Jsl.FxCop
{
    using System;
    using Jsl.FxCop.Targets;
    using Microsoft.FxCop.Sdk;
    using Microsoft.VisualStudio.CodeAnalysis.Extensibility;
    using Microsoft.VisualStudio.TestTools.UnitTesting;

    [TestClass]
    public partial class TestCallComponentsDispose
    {
        [TestMethod]
        public void Different_valid_code()
        {
            const string Code = @"
using System.ComponentModel;
using System.Windows.Forms;

public class Class : Form
{
    private IContainer components;

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            if (components != null)
            {
                components.Dispose();
            }

            try
            {
                this.Text = ""xyz"";
            }
            catch (System.Exception ex)
            {
                System.Windows.Forms.MessageBox.Show(ex.ToString());
            }
        }

        base.Dispose(disposing);
    }
}";

            Test(
                Code,
                (debugMode, method) =>
                {
                    new CallComponentsDispose()
                        .RunMethodRule(method)
                        .HasNoMoreProblems();
                });
        }

        [TestMethod]
        public void Different_valid_code2()
        {
            const string Code = @"
using System.ComponentModel;
using System.Windows.Forms;

public class Class : Form
{
    private System.ComponentModel.IContainer components = null;

    protected override void Dispose(bool disposing)
    {
        if (disposing && (this.components != null))
        {
            this.components.Dispose();
        }

        base.Dispose(disposing);
    }
}";

            Test(
                Code,
                (debugMode, method) =>
                {
                    new CallComponentsDispose()
                        .RunMethodRule(method)
                        .HasNoMoreProblems();
                });
        }

        [TestMethod]
        public void Type_level_check_OK_if_Dispose_method_exists()
        {
            new CallComponentsDispose()
                .RunTypeRule(typeof(CallComponentsDispose_everything_is_correct))
                .HasNoMoreProblems();
        }

        [TestMethod]
        public void Fail_if_components_field_exists_and_no_Dispose_override()
        {
            new CallComponentsDispose()
                .RunTypeRule(typeof(CallComponentsDispose_no_Dispose_override))
                .HasProblem(
                    100,
                    FixCategories.NonBreaking,
                    MessageLevel.Error,
                    "Override the Dispose method and call components.Dispose.")
                .HasNoMoreProblems();
        }

        [TestMethod]
        public void Needs_null_check_before_disposing()
        {
            const string Code = @"
using System.ComponentModel;
using System.Windows.Forms;

public class Class : Form
{
    private IContainer components;

    protected override void Dispose(bool disposing)
    {
        this.components.Dispose();
    }

    private void InitializeComponent()
    {
        this.components = new Container();
    }
}";

            Test(
                Code,
                (debugMode, method) =>
                {
                    new CallComponentsDispose()
                        .RunMethodRule(method)
                        .HasProblem(
                            100,
                            FixCategories.NonBreaking,
                            MessageLevel.Error,
                            "You must check that the components field is not null before disposing it.")
                        .HasNoMoreProblems();
                });
        }

        [TestMethod]
        public void Multiple_paths_lead_to_component_Dispose()
        {
            const string Code = @"
using System.ComponentModel;
using System.Windows.Forms;

public class Class : Form
{
    private IContainer components;

    protected override void Dispose(bool disposing)
    {
        if (disposing || (this.components != null))     // NOTE: || instead of &&
        {
            this.components.Dispose();
        }

        base.Dispose(disposing);
    }

    private void InitializeComponent()
    {
        this.components = new Container();
    }
}";
            const string Message = @"Modify the code to dispose the components field like this:
if (disposing)
{
  if (this.components != null)
  {
    this.components.Dispose();
  }
}";

            Test(
                Code,
                (debugMode, method) =>
                {
                    // NOTE: Only happens in Release mode.
                    new CallComponentsDispose()
                        .RunMethodRule(method)
                        .HasProblem(
                            100,
                            FixCategories.NonBreaking,
                            MessageLevel.Error,
                            Message)
                        .HasNoMoreProblems();
                });
        }

        [TestMethod]
        public void Statement_before_the_Dispose_should_be_Branch()
        {
            const string Code = @"
using System.ComponentModel;
using System.Windows.Forms;

public class Class : Form
{
    private IContainer components;

    protected override void Dispose(bool disposing)
    {
        System.Console.Write(1);
        this.components.Dispose();
    }
}";

            Test(
                Code,
                (debugMode, method) =>
                {
                    // NOTE: Only happens in Release mode.
                    new CallComponentsDispose()
                        .RunMethodRule(method)
                        .HasProblem(
                            100,
                            FixCategories.NonBreaking,
                            MessageLevel.Error,
                            @"You must check that the components field is not null before disposing it.")
                        .HasNoMoreProblems();
                });
        }

        [TestMethod]
        public void Dispose_is_called_on_IContainer()
        {
            const string Code = @"
using System.ComponentModel;
using System.Windows.Forms;

public class Class : Form
{
    private IContainer components;

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
	        if (this.components != null)
	        {
		        this.components.Dispose();
	        }
        }
    }

    private void InitializeComponent()
    {
        this.components = new Container();
    }
}";

            Test(
                Code,
                (debugMode, method) =>
                {
                    new CallComponentsDispose()
                        .RunMethodRule(method)
                        .HasNoMoreProblems();
                });
        }

        [TestMethod]
        public void Dispose_is_called_on_Container()
        {
            const string Code = @"
using System.ComponentModel;
using System.Windows.Forms;

public class Class : Form
{
    private Container components;

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            if (this.components != null)
            {
                this.components.Dispose();
            }
        }
    }

    private void InitializeComponent()
    {
        this.components = new Container();
    }
}";

            Test(
                Code,
                (debugMode, method) =>
                {
                    new CallComponentsDispose()
                        .RunMethodRule(method)
                        .HasNoMoreProblems();
                });
        }

        [TestMethod]
        public void Dispose_is_called_and_components_is_set_to_null()
        {
            const string Code = @"
using System.ComponentModel;
using System.Windows.Forms;

public class Class : Form
{
    private IContainer components;

    protected override void Dispose(bool disposing)
    {
        if (this.components != null)
        {
            this.components.Dispose();
            this.components = null;
        }
    }

    private void InitializeComponent()
    {
        this.components = new Container();
    }
}";

            Test(
                Code,
                (debugMode, method) =>
                {
                    new CallComponentsDispose()
                        .RunMethodRule(method)
                        .HasNoMoreProblems();
                });
        }

        [TestMethod]
        public void Components_null_check_is_inside_same_if_as_disposing_check()
        {
            const string Code = @"
using System.ComponentModel;
using System.Windows.Forms;

public class Class : Form
{
    private IContainer components;

    protected override void Dispose(bool disposing)
    {
        if (disposing && (this.components != null))
        {
            this.components.Dispose();
        }

        base.Dispose(disposing);
    }

    private void InitializeComponent()
    {
        this.components = new Container();
    }
}";

            Test(
                Code,
                (debugMode, method) =>
                {
                    new CallComponentsDispose()
                        .RunMethodRule(method)
                        .HasNoMoreProblems();
                });
        }

        [TestMethod]
        public void Components_null_check_is_inside_same_if_as_disposing_check_with_extra_code()
        {
            const string Code = @"
using System.ComponentModel;
using System.Windows.Forms;

public class Class : Form
{
    private IContainer components;

    protected override void Dispose(bool disposing)
    {
        if (disposing && (this.components != null))
        {
            this.components.Dispose();
            System.Console.WriteLine();
        }

        base.Dispose(disposing);
    }

    private void InitializeComponent()
    {
        this.components = new Container();
    }
}";
            const string Message = @"The this.components.Dispose() should be the only thing in an if statement:
if (disposing)
{
  if (this.components != null)
  {
    this.components.Dispose();
  }
}";

            Test(
                Code,
                (debugMode, method) =>
                {
                    new CallComponentsDispose()
                        .RunMethodRule(method)
                        .HasProblem(
                            100,
                            FixCategories.NonBreaking,
                            MessageLevel.Error,
                            Message)
                        .HasNoMoreProblems();
                });
        }

        [TestMethod]
        public void If_the_components_field_isnt_initialized_in_the_InitializeComponent_method_then_it_doesnt_need_to_be_disposed()
        {
            const string Code = @"
using System.ComponentModel;
using System.Windows.Forms;

public class Class : Control
{
    private Container components = null;

    protected override void Dispose(bool disposing)
    {
        base.Dispose(disposing);
    }

    private void InitializeComponent()
    {
    }
}";

            Test(
                Code,
                (debugMode, method) =>
                {
                    new CallComponentsDispose()
                        .RunMethodRule(method)
                        .HasNoMoreProblems();
                });
        }

        [TestMethod]
        public void Must_dispose_components_field()
        {
            const string Code = @"
using System.ComponentModel;
using System.Windows.Forms;

public class Class : Form
{
    private IContainer components;

    protected override void Dispose(bool disposing)
    {
        if (this.components != null)
        {
        }
    }

    private void InitializeComponent()
    {
        this.components = new Container();
    }
}";
            const string Message = @"Modify the code to dispose the components field like this:
if (disposing)
{
  if (this.components != null)
  {
    this.components.Dispose();
  }
}";

            Test(
                Code,
                (debugMode, method) =>
                {
                    new CallComponentsDispose()
                        .RunMethodRule(method)
                        .HasProblem(
                            100,
                            FixCategories.NonBreaking,
                            MessageLevel.Error,
                            Message)
                        .HasNoMoreProblems();
                });
        }

        [TestMethod]
        public void Should_only_dispose_once()
        {
            const string Code = @"
using System.ComponentModel;
using System.Windows.Forms;

public class Class : Form
{
    private IContainer components;

    protected override void Dispose(bool disposing)
    {
        if (this.components != null)
        {
            this.components.Dispose();
        }

        this.components.Dispose();
    }

    private void InitializeComponent()
    {
        this.components = new Container();
    }
}";

            Test(
                Code,
                (debugMode, method) =>
                {
                    new CallComponentsDispose()
                        .RunMethodRule(method)
                        .HasProblem(
                            100,
                            FixCategories.NonBreaking,
                            MessageLevel.Error,
                            "There should only be one call to dispose the components field.")
                    .HasNoMoreProblems();
                });
        }

        [TestMethod]
        public void Dispose_must_be_only_statement_in_if()
        {
            const string Code = @"
using System.ComponentModel;
using System.Windows.Forms;

public class Class : Form
{
    private IContainer components;

    protected override void Dispose(bool disposing)
    {
        if (this.components != null)
        {
            this.components.Dispose();
            this.Visible = false;
        }
    }

    private void InitializeComponent()
    {
        this.components = new Container();
    }
}";
            const string Message = @"The this.components.Dispose() should be the only thing in an if statement:
if (disposing)
{
  if (this.components != null)
  {
    this.components.Dispose();
  }
}";

            Test(
                Code,
                (debugMode, method) =>
                {
                    new CallComponentsDispose()
                        .RunMethodRule(method)
                        .HasProblem(
                            100,
                            FixCategories.NonBreaking,
                            MessageLevel.Error,
                            Message)
                        .HasNoMoreProblems();
                });
        }

        [TestMethod]
        public void Dont_use_components_field_if_not_created_and_disposed()
        {
            const string Code = @"
using System.ComponentModel;
using System.Windows.Forms;

public class Class : Form
{
    private IContainer components;

    protected override void Dispose(bool disposing)
    {
        if (disposing && (this.components != null))
        {
            System.Console.WriteLine();
        }
    }

    private void InitializeComponent()
    {
    }
}";

            Test(
                Code,
                (debugMode, method) =>
                {
                    new CallComponentsDispose()
                        .RunMethodRule(method)
                        .HasProblem(
                            100,
                            FixCategories.NonBreaking,
                            MessageLevel.Error,
                            "The components field is never created and never disposed so it shouldn't be used in the Dispose(bool) method.")
                        .HasNoMoreProblems();
                });
        }

        #region Private Methods
        private static void Test(string classCode, Action<bool, Method> action)
        {
            var codeTester = new CodeTestHelper(
                new CompileHelper()
                    .AddSource(classCode)
                    .AddAssemblyReference("System.Windows.Forms.dll"));
            codeTester.MethodNameToTest = "Dispose";
            codeTester.Test(action);
        }
        #endregion Private Methods
    }
}